const app = angular.module("myApp",['ui.bootstrap', 'ui.bootstrap.treeview']);

app.config( [
    '$compileProvider',
    function( $compileProvider )
    {
        $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|blob|sms|data):/);
        // Angular v1.2 之前使用 $compileProvider.urlSanitizationWhitelist(...)
    }
]);

app.controller("manageController2",['$scope','$compile','$sce',function($scope,$compile,$sce){
    $scope.query={
        fault:FaultNode.faults[0]
    };

    $scope.filterOfFault = function (fault) {
        if($scope.query.faultType && $scope.query.faultType!= ''){
            if(fault.faultCategory != $scope.query.faultType){
                return false;
            }
        }
        if($scope.query.faultModule && $scope.query.faultModule!= ''){
            if(fault.faultModule != $scope.query.faultModule){
                return false;
            }
        }
        return true;
    };


    $scope.faultModules=[];
    $scope.refreshModules = function () {
        getCache("fault_module",val=>{
            $scope.faultModules=val.map(x=>{
                if(x.indexOf(':')<0){
                    return {
                        category:'',
                        name:x,
                    }
                }
                let arr = x.split(':',2);
                return ({
                    category:arr[0],
                    name:arr[1],
                })
            });
            $scope.faultModules.splice(0,0,'');
        },(callback,cache,name)=>{
            getConfigure('module',data=>{
                cache[name]={
                    lastTimestamp:new Date().getTime(),
                    value:data
                };
                callback(data);
            });
        },5000);
    };
    $scope.refreshModules();

    $scope.refreshFaults = function () {
        FaultNode.loadFaults.callbacks = [()=>$scope.faults=FaultNode.faults];
        FaultNode.loadFaults();

        ComponentNode.loadComponents();

        $scope.refreshModules();
    };

    $scope.sortByOutId = function (f) {
        if(!f.outId){
            return 0;
        }
        return parseInt(f.outId,10);
    };

    $scope.toJSON = function (val) {
        return angular.toJson(val,true);
    };

    $scope.graphModel = 1;

    // $scope.faults=FaultNode.faults;
    FaultNode.loadFaults.callbacks.push(()=>$scope.faults=FaultNode.faults);
    FaultNode.loadFaults.callbacks.push(()=>$scope.query.fault=FaultNode.faults[0]);
    FaultNode.loadFaults();

    $scope.components=ComponentNode.components;
    ComponentNode.loadComponents.callbacks.push(()=>$scope.components=ComponentNode.components);
    ComponentNode.loadComponents();

    $scope.graphTree = new TreeView();
    $scope.d3Graph = new D3Graph();
    $scope.$watch('query',function () {
        console.info($scope.query);
        $scope.reloadData();
    },true);

    $scope.$watch('graphTree.selectedNode',function () {
        let data = $scope.graphTree.selectedNode;
        if($scope.graph){
            let template = $scope.graph.getNode(data.referId).getReadonlyTemplate(data.referEdge,$scope.graph);
            let tp = $compile( template )( $scope );
            angular.element("#nodeDetail2").html("").append( tp );
        }
    },true);

    $scope.reloadData = function(){
        if($scope.query.fault)
            Graph.loadGraph($scope.query.fault.id,function (g) {
                let session = neo4jService.getSession();
                g.markSubGraph().then(()=>{
                    return Promise.all([... g.nodes.values()].filter(n=>n.$is_sub_graph && !n.$removed)
                        .map(n=>new Promise((resolve,reject)=>{
                            return MyNode.getAncestors(session,n.id).then(nd=>{
                                nd.filter(ndd=>ndd.$type=="Fault").forEach(nddx=>{
                                    n.$faults = n.$faults || new Set();
                                    n.$faults.add(nddx);
                                });
                                resolve();
                            })
                        })));
                }).then(function () {
                    return Promise.all([... g.nodes.values()].filter(n=>(n.$is_sub_graph||n.$type=="Fault") && n.$type!='ReasonGroup')
                        .map(n=>new Promise((resolve,reject)=>{
                            if (n.$is_sub_graph){
                                $.get(`http://127.0.0.1:8080/feature/${n.id}`,function (data) {
                                    n = $.extend(n,data);
                                    g.addNode(n);
                                    resolve(n);
                                });
                            } else {
                                $.get(`http://127.0.0.1:8080/reason/${n.id}`,function (data) {
                                    n = $.extend(n,data);
                                    g.addNode(n);
                                    resolve(n);
                                });
                            }
                        })))
                }).then(()=>{
                    $scope.graph = g;
                    $scope.graphTree.nodes = [$scope.graph.toJsonTree()];
                    $scope.graphTree.collapseAll();
                    console.info($scope.graphTree);

                    $scope.$apply();
                    if ($scope.graphModel==1){
                        $scope.d3Graph.reset();
                    }
                    $scope.d3Graph.refresh($scope.graph);
                });
            });
    };

    $scope.displayAllTree = function () {
        // let gs = [];
        // let gts =  FaultNode.faults.length;
        // let gtn = 0;
        // for (let f of FaultNode.faults){
        //     Graph.loadGraph(f.id,(g)=>{
        //         gs.push(g);
        //         gtn ++;
        //         if(gtn==gts){
        //             $scope.graphTree.nodes = gs.map(g=>g.toJsonTree());
        //             $scope.graphTree.collapseAll();
        //         }
        //     });
        // }
        Graph.loadAllGraph().then(graphGroup=>{
            $scope.graphTree.nodes = [graphGroup.toJsonTree()];
            $scope.graphTree.collapseAll();

            $scope.$apply();
            if ($scope.graphModel==1){
                $scope.d3Graph.reset();
            }
            $scope.d3Graph.refresh(graphGroup);
        });
    };

    $scope.href = '';
    $scope.generateDownloadHref = function () {
        // let serializer = new XMLSerializer();
        // let xmlString = serializer.serializeToString(d3.select('svg').node());
        // let imgData = 'data:image/svg+xml;base64,' + btoa(xmlString);
        let svg = d3.select("svg")
            .attr("title", "知识库")
            .attr("version", 1.1)
            .attr("xmlns", "http://www.w3.org/2000/svg")
            .node().innerHTML;
        let cover = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" class="neo4jd3-graph" version="1.0"><defs>
    <style type="text/css"><![CDATA[
       * {
  margin: 0px;
  padding: 0px;
}

body {
  font: 13px 'Helvetica Neue',Helvetica,Arial,sans-serif;
  font-size: 20px;
}

.neo4jd3-graph {
  border: 1px solid #ddd;
  border-radius: 5px;
}

.neo4jd3-info {
  font-size: 16px;
  padding: 10px;
  position: absolute;
}
.neo4jd3-info a {
  border: 1px solid;
  display: inline-block;
  font-size: 14px;
  line-height: 1.428571429;
  margin-left: 5px;
  margin-top: 5px;
  padding: 6px 12px;
}
.neo4jd3-info a.class {
  color: white;
}
.neo4jd3-info a.property {
  background-color: #fff;
  border-color: #ccc;
  color: #333;
}
.neo4jd3-info a.btn {
  margin-left: 5px;
  margin-top: 5px;
  opacity: 1;
}
.neo4jd3-info a.info {
  background-color: #a5abb6;
  border: 1px solid #9aa1ac;
  color: white;
}

.node.node-highlighted .ring {
  -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)';
  filter: alpha(opacity=50);
  opacity: .5;
  stroke: #888;
  stroke-width: 12px;
}
.node .outline {
  cursor: pointer;
  fill: #a5abb6;
  pointer-events: all;
  stroke: #9aa1ac;
  stroke-width: 2px;
}
.node .ring {
  fill: none;
  -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)';
  filter: alpha(opacity=0);
  opacity: 0;
  stroke: #6ac6ff;
  stroke-width: 8px;
}
.node .text.icon {
  font-family: FontAwesome;
}

.node.selected .ring,
.node:hover .ring {
  -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=30)';
  filter: alpha(opacity=30);
  opacity: .3;
}

.relationship {
  cursor: default;
}
.relationship line {
  stroke: #aaa;
}
.relationship .outline {
  cursor: default;
}
.relationship .overlay {
  cursor: default;
  fill: #6ac6ff;
  -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)';
  filter: alpha(opacity=0);
  opacity: 0;
}
.relationship text {
  cursor: default;
}

.relationship.selected .overlay,
.relationship:hover .overlay {
  -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=30)';
  filter: alpha(opacity=30);
  opacity: .3;
}

svg {
  cursor: move;
}

    ]]></style>
  </defs>
  ${svg}
  </svg>`;
        let blob = new Blob([cover]);
        $scope.href = URL.createObjectURL(blob);
        // $scope.href ="data:image/svg+xml;base64,\n" + btoa(unescape(encodeURIComponent(cover)));
    };
}]);

class D3Graph {
    constructor(){
       this.reset();
    }

    refresh(graph){
        this.d3data = new D3Transfer(graph);
        // d3.selectAll('#neo4jd3 .relationship').remove();
        // d3.selectAll('#neo4jd3 .node').remove();
        this.neo4jd3.updateWithD3Data(this.d3data);
    }

    reset(){
        if (this.neo4jd3){
            delete this.neo4jd3;
        }
        d3.selectAll('#neo4jd3 g.relationships *').remove();
        d3.selectAll('#neo4jd3 g.nodes *').remove();
        this.neo4jd3 = new Neo4jd3('#neo4jd3', {
            highlight: [],
            minCollision: 60,
            nodeRadius: 25,
            neo4jData:{
                "results": [{
                    "columns": ["user", "entity"],
                    "data": [{
                        "graph": {
                            "nodes":[],
                            "relationships":[]
                        }
                    }]
                }]
            },
            onNodeDoubleClick: function(node) {
                switch(node.id) {
                    case '25':
                        // Google
                        window.open(node.properties.url, '_blank');
                        break;
                    default:
                        var maxNodes = 5,
                            data = neo4jd3.randomD3Data(node, maxNodes);
                        neo4jd3.updateWithD3Data(data);
                        break;
                }
            },
            onRelationshipDoubleClick: function(relationship) {
                console.log('double click on relationship: ' + JSON.stringify(relationship));
            },
            zoomFit: true
        });
    }
}

class D3SimpleForceChart {
    constructor(chartRef,data) {
        this.chartRef = document.querySelector(chartRef);
        this.data = data;
        const containerWidth = this.chartRef.parentElement.offsetWidth;
        const margin = { top: 60, right: 60, bottom: 60, left: 60 };
        const width = containerWidth - margin.left - margin.right;
        const height = 700 - margin.top - margin.bottom;
        let chart = d3
            .select(this.chartRef)
            .attr('width', width + margin.left + margin.right)
            .attr('height', height + margin.top + margin.bottom);
        let g = chart
            .append('g')
            .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); // 设最外包层在总图上的相对位置
        let simulation = d3
            .forceSimulation() // 构建力导向图
            .force(
                'link',
                d3
                    .forceLink()
                    .id(function(d, i) {
                        return i
                    })
                    .distance(function(d) {
                        return d.value * 50
                    })
            )
            .force('charge', d3.forceManyBody())
            .force('center', d3.forceCenter(width / 2, height / 2))
            .alpha(1)
            .alphaDecay(0.001);

        let z = d3.scaleOrdinal(d3.schemeCategory10) ;// 通用线条的颜色

        let link = g
            .append('g') // 画连接线
            .attr('class', 'links')
            .selectAll('line')
            .data(data.edges)
            .enter()
            .append('line');

        let linkText = g
            .append('g') // 画连接连上面的关系文字
            .attr('class', 'link-text')
            .selectAll('text')
            .data(data.edges)
            .enter()
            .append('text')
            .text(function(d) {
                return d.relation
            });

        let node = g
            .append('g') // 画圆圈和文字
            .attr('class', 'nodes')
            .selectAll('g')
            .data(data.nodes)
            .enter()
            .append('g')
            // .on('mouseover', function(d, i) {
            //     //显示连接线上的文字
            //     linkText.style('fill-opacity', function(edge) {
            //         if (edge.source === d || edge.target === d) {
            //             return 1
            //         }
            //     });
            //     //连接线加粗
            //     link
            //         .style('stroke-width', function(edge) {
            //             if (edge.source === d || edge.target === d) {
            //                 return '2px'
            //             }
            //         })
            //         .style('stroke', function(edge) {
            //             if (edge.source === d || edge.target === d) {
            //                 return '#000'
            //             }
            //         })
            // })
            // .on('mouseout', function(d, i) {
            //     //隐去连接线上的文字
            //     linkText.style('fill-opacity', function(edge) {
            //         if (edge.source === d || edge.target === d) {
            //             return 0
            //         }
            //     });
            //     //连接线减粗
            //     link
            //         .style('stroke-width', function(edge) {
            //             if (edge.source === d || edge.target === d) {
            //                 return '1px'
            //             }
            //         })
            //         .style('stroke', function(edge) {
            //             if (edge.source === d || edge.target === d) {
            //                 return '#ddd'
            //             }
            //         })
            // })
            .call(
                d3
                    .drag()
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
            );

        //显示连接线上的文字
        linkText.style('fill-opacity', 1);

        link
            .style('stroke-width', function(edge) {
                // if (edge.source === d || edge.target === d) {
                return '2px'
                // }
            })
            .style('stroke', function(edge) {
                // if (edge.source === d || edge.target === d) {
                return '#000'
                // }
            });

        node
            .append('circle')
            .attr('r', 45)
            .attr('fill', function(d, i) {
                return z(i)
            });

        node
            .append('text')
            .attr('fill', function(d, i) {
                return z(i)
            })
            .attr('y', -20)
            .attr('dy', '.71em')
            .text(function(d) {
                return d.name
            });

        simulation // 初始化力导向图
            .nodes(data.nodes)
            .on('tick', ticked);

        simulation.force('link').links(data.edges);

        chart
            .append('g') // 输出标题
            .attr('class', 'bar--title')
            .append('text')
            .attr('fill', '#000')
            .attr('font-size', '16px')
            .attr('font-weight', '700')
            .attr('text-anchor', 'middle')
            .attr('x', containerWidth / 2)
            .attr('y', 20)
            .text('人物关系图');

        function ticked() {
            // 力导向图变化函数，让力学图不断更新
            link
                .attr('x1', function(d) {
                    return d.source.x
                })
                .attr('y1', function(d) {
                    return d.source.y
                })
                .attr('x2', function(d) {
                    return d.target.x
                })
                .attr('y2', function(d) {
                    return d.target.y
                });
            linkText
                .attr('x', function(d) {
                    return (d.source.x + d.target.x) / 2
                })
                .attr('y', function(d) {
                    return (d.source.y + d.target.y) / 2
                });
            node.attr('transform', function(d) {
                return 'translate(' + d.x + ',' + d.y + ')'
            })
        }

        function dragstarted(d) {
            if (!d3.event.active) {
                simulation.alphaTarget(0.3).restart()
            }
            d.fx = d.x;
            d.fy = d.y
        }

        function dragged(d) {
            d.fx = d3.event.x;
            d.fy = d3.event.y
        }

        function dragended(d) {
            if (!d3.event.active) {
                simulation.alphaTarget(0)
            }
            d.fx = null;
            d.fy = null
        }
    }
}

class D3Transfer {
    constructor(graph){
        this.nodes = [... graph.nodes.values()].map(node=> {
           return {
               id:node.id,
               labels:[node.$type],
               properties:node
           }
        });
        this.relationships = graph.edges.map(edge=>{
            let edgeProps = $.extend({},edge);
            delete edgeProps.graph;

            let fromNode = graph.getNode(edge.from);
            let endNode = graph.getNode(edge.to);
            let fault = graph.getRoot();

            if (fromNode==null || endNode==null){
                return null;
            }

            if (endNode.$is_sub_graph) {
                edgeProps["概率"] = `${(endNode.defaultFrequency(fault.id)[fault.id] / endNode.defaultQuantum() * 100).toFixed(2)}%`;
            }

            if (endNode.$type == 'Reason'){
                if (fault.frequency!=null && fault.quantum!=null){
                    edgeProps["概率"] = `${(fault.frequency[edge.to] / fault.quantum * 100).toFixed(2)}%`;
                } else {
                    edgeProps["概率"] = `${(endNode.probability * 100).toFixed(2)}%`;
                }
            }

            return {
                "id": edge.id,
                "type": edge.linkType(),
                "startNode": edge.from,
                "endNode": edge.to,
                "properties": edgeProps,
                "source": edge.from,
                "target": edge.to,
                "linknum": 1
            };
        }).filter(x=>x!=null);
    }
}

class GraphTransfer {
    constructor(g){
        this.g = g;
        this.nodes = [... g.nodes.values()];
        this.edges = g.edges.map(edge=>{
            return {
                source:GraphTransfer.indexOf(this.nodes,edge.from),
                target:GraphTransfer.indexOf(this.nodes,edge.to),
                relation:edge.linkType(),
                id:edge.id,
                value:GraphTransfer.weightOfLink(edge.linkType())
            }
        });
    }

    static indexOf(nodes,id){
        return nodes.findIndex(node=>node.id==id);
    }

    static weightOfLink(linkType){
        switch (linkType){
            case 'hasFeatures':
            case 'hasReasons':
                return 10;
            case 'contains':
                return 6;
            case 'of':
                return 3;
        }
        return 8;
    }
}

// 找到支持的方法, 使用需要全屏的 element 调用
function launchFullScreen(element) {
    if(element.requestFullscreen) {
        element.requestFullscreen();
    } else if(element.mozRequestFullScreen) {
        element.mozRequestFullScreen();
    } else if(element.webkitRequestFullscreen) {
        element.webkitRequestFullscreen();
    } else if(element.msRequestFullscreen) {
        element.msRequestFullscreen();
    }
}

// 退出 fullscreen
function exitFullscreen() {
    if(document.exitFullscreen) {
        document.exitFullscreen();
    } else if(document.mozExitFullScreen) {
        document.mozExitFullScreen();
    } else if(document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
    }
}
